package de.androidbuch.amando.gui;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.Paint.Style;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.MyLocationOverlay;
import com.google.android.maps.Overlay;

import de.androidbuch.amando.R;
import de.androidbuch.amando.common.GpsData;
import de.androidbuch.amando.kontakt.GeoKontakt;
import de.androidbuch.amando.kontakt.GeoKontaktSpeicher;
import de.androidbuch.amando.kontakt.TelefonnummernHelfer;
import de.androidbuch.amando.kontakt.db.GeoKontaktTbl;
import de.androidbuch.amando.receiver.SmsBroadcastReceiver;
import de.androidbuch.amando.services.impl.NetzwerkServiceImpl;

/**
 * Zeigt Google Maps in einer MapActivity an.
 * <p>
 * Im Emulator zum Beispiel die Koordinaten mit <br>
 * geo fix 7.1 51 300 <br>
 * setzen um etwas zu sehen.
 * 
 * @author David Müller, Arno Becker, 2010 visionera gmbh
 */
public class KarteAnzeigen extends MapActivity {

  /** Kuerzel fuers Logging. */
  private static final String TAG = KarteAnzeigen.class
      .getSimpleName();

  /**
   * Schlüssel des zu bearbeitenden Kontakts, der per Intent
   * über geben wird.
   */
  static final String IN_PARAM_KONTAKT_ID = "KONTAKT_ID";
  
  /** 
   * Schlüssel für Intent-Parameter: KarteAnzeigen im 
   * Simulationsmodus starten oder nicht.
   */
  static final String IN_PARAM_SIMULATIONSMODUS = 
    "SimulationsModus";
  
  /** 
   * Schlüssel für Callback-Parameter aus dem 
   * GeoPositionsService.
   */
  public static final String IN_PARAM_GEO_POSITION = 
    "location";

  /** 
   * Binder, der die Verbindung zum NetzwerkService
   * herstellt.
   */
  private NetzwerkServiceImpl.NetzwerkServiceBinder 
    mNetzwerkBinder;

  /**
   * GeoKontakt-Id des Kontakts, der in der Karte angezeigt 
   * wird.
   */
  private long mGeoKontaktId;
  
  /**
   * true, wenn die Position des Bekannten ständig in der 
   * Karte aktualisiert werden soll.
   */
  private boolean mPositionNachverfolgen;
  
  /** true, wenn Amando im Simulationsmodus laufen soll. */
  private boolean mSimulationsModus;
  
  /** GeoKontakt, der in der Karte angezeigt wird. */
  private GeoKontakt mFreundKontakt;

  /** MapView zur Darstellung der Karte. */
  private MapView mMapView;
  
  /** MapController zur Steuerung der Kartenfunktionen. */
  private MapController mMapController;
  
  /** 
   * MyLocationOverlay für Detailinformationen in der Karte.
   */
  private MyLocationOverlay mMyLocationOverlay;
  
  /** 
   * GeoKontaktOverlay für Detailinformationen in der Karte.
   */
  private GeoKontaktOverlay mMapViewOverlay;


  /**
   * Baut eine Verbindung zum lokalen Service für die
   * Position des Freundes auf. Der Service laeuft im
   * gleichen Prozess wie diese Activity. Daher wird er
   * automatisch beendet, wenn der Prozess der Activity
   * beendet wird.
   */
  private ServiceConnection mNetzwerkServiceConnection =
      new ServiceConnection() {
    // Wird aufgerufen, sobald die Verbindung zum lokalen
    // Service steht.
    public void onServiceConnected(ComponentName className,
        IBinder binder) {
      Log.d(TAG, "KarteAnzeigen->onServiceConnected(): " +
          "entered...");
      mNetzwerkBinder = 
        (NetzwerkServiceImpl.NetzwerkServiceBinder) binder;
      mNetzwerkBinder.setAnzeigeCallbackHandler(
          mKarteAnzeigenCallbackHandler);
      mNetzwerkBinder.empfangeFreundPosition(
          mFreundKontakt.mobilnummer,
          ermittleEigeneMobilnummer());
    }
  
    // Wird aufgerufen, sobald die Verbindung zum Service
    // unterbrochen wird.
    // Dies passiert nur, wenn der Prozess, der den Service
    // gestartet hat, stirbt.
    // Da dies ein lokaler Service ist, läuft er im selben
    // Prozess wie diese Activity.
    // Daher kann die Methode niemals aufgrufen werden und
    // muss nicht implementiert
    // werden.
    public void onServiceDisconnected(
        ComponentName className) {
      // unerreichbar...
    }
  };

  /**
   * Handler zur Verarbeitung von Callbacks aus dem
   * NetzwerkService mit Hilfe von Message-Objekten. Der
   * NetzwerkService empfängt die Positionsänderungen der
   * Clients, der die SMS an diese Anwendung geschickt hat.
   * Wird diese Activity angezeigt, so wird dem
   * NetzwerkService dieser Callback-Handler übergeben.
   * Empfängt der NetzwerkService eine Positionsänderung,
   * stellt er ein Message-Objekt in die Message-Queue des
   * Handlers. Dadurch wird die hier überschriebene Methode
   * 'handleMessage()' aufgerufen, in der die
   * Positionänderung des Clients in der Karte aktualisiert
   * wird.
   */
  private final Handler mKarteAnzeigenCallbackHandler = 
    new Handler() {
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      Log.d(TAG, "Handler->handleMessage(): entered...");
      final Bundle bundle = msg.getData();
      if (bundle != null) {
        final Location location = (Location) bundle
          .get(KarteAnzeigen.IN_PARAM_GEO_POSITION);
        if (location == null) {
          Log.w(TAG,
            "Handler->handleMessage(): Message-Objekt " +
            "enthielt keine Ortsdaten: " + msg);
          return;
        }

        mFreundKontakt.letztePosition.gpsData = 
          GpsData.valueOf(location);
        mFreundKontakt.mobilnummer = 
          bundle.getString(GeoKontakt.KEY_MOBILNUMMER);

        mMapView.invalidate();
      } else {
        Log.w(TAG,
          "Handler->handleMessage(): Message-Objekt " +
          "enthielt keine Daten: " + msg);
      }
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d(TAG, "KarteAnzeigen->onCreate(): entered...");
  
    setContentView(R.layout.karte_anzeigen);
       
    verarbeiteIntentBundle(getIntent().getExtras());
    
    initMapView();
  
    // fuege der MapView das Overlay fuer die
    // empfangene Position hinzu
    initGeokontaktOverlay();
  
    // Zeige die eigene Position mit Hilfe
    // des Overlays aus der Google Api
    initMyLocationOverlay();
  }
  
  /**
   * Liest die im Intent übergebenen Daten aus und 
   * speichert sie in Instanzvariablen ab.
   * 
   * Am wichtigsten ist dabei die id des Geokontaktes, 
   * damit im nächsten Schritt die Geokontaktdaten
   * geladen werden können.
   * 
   * @param extras Bundle mit Geokontakt-Id, 
   *   Simulationsmodus und dem Position-Nachverfolgen-Flag.
   */
  private void verarbeiteIntentBundle(final Bundle extras) {
    if (extras != null) {
      if (extras.containsKey(IN_PARAM_KONTAKT_ID)) {
        mGeoKontaktId = extras.getLong(IN_PARAM_KONTAKT_ID);
        Log.d(TAG, "Aufruf mit Kontakt-Id " + 
            mGeoKontaktId);
      }
      if (extras.containsKey(SmsBroadcastReceiver.
          KEY_NACHVERFOLGEN)) {
        mPositionNachverfolgen = 
          extras.getBoolean(SmsBroadcastReceiver.
              KEY_NACHVERFOLGEN);
      }
      
      if (extras.containsKey(IN_PARAM_SIMULATIONSMODUS)) {
        mSimulationsModus = 
          extras.getBoolean(IN_PARAM_SIMULATIONSMODUS);
      }  
    }
  }

  /**
   * Stellt die MapView und deren Controller nach unseren
   * Vorlieben ein.
   * 
   * <li>Zoomstufe</li>
   * <li>Zoomkontrollen Anzeige</li>
   * <li>Typ der angezeigten Karte</li>
   * 
   */
  private void initMapView() {
    mMapView = (MapView) findViewById(R.id.mapview_karte); 
    mMapController = mMapView.getController();
  
    final int maxZoomlevel = mMapView.getMaxZoomLevel();
    Log.d(TAG,
        "initMapView(): Max Zoom-Level: " +
        maxZoomlevel);
    mMapController.setZoom(maxZoomlevel - 4); // zoom
    
    // Blende bei Klick die Zoomkontrollen ein
    mMapView.setBuiltInZoomControls(true);
    
//    mMapView.setSatellite(true); // Satellitenbild
    mMapView.setStreetView(true); // Strassenansicht
  }

  /**
   * Fuegt den Google Api Overlay zur Anzeige der eigenen
   * Position hinzu.
   */
  private void initMyLocationOverlay() { 
    mMyLocationOverlay = new MyLocationOverlay(this,
        mMapView) {
      
      /** 
       * Ueberschriebene callback Methode des Location
       * Managers, um auf die eigene Position 
       * zu scrollen.
       */
      @Override
      public void onLocationChanged(Location neuePosition) {
        super.onLocationChanged(neuePosition);
        Log.d(TAG, "MyLocationOverlay->onLocationChanged " +
            "aufgerufen mit: " + neuePosition);
        final GeoPoint eigenePosition = new GeoPoint(
            (int) (neuePosition.getLatitude() * 1E6),
            (int) (neuePosition.getLongitude() * 1E6));
  
        // hiermit wird die eigene Position immer
        // im Zentrum angezeigt:
        mMapController.animateTo(eigenePosition);
      }
    };
    
    mMapView.getOverlays().add(mMyLocationOverlay);
    mMyLocationOverlay.enableMyLocation();
    mMyLocationOverlay.enableCompass();
    
    // nach der ersten Festlegung der eigenen Position
    // diese in der Mitte anzeigen
    mMyLocationOverlay.runOnFirstFix(new Runnable() {
      public void run() {
        mMapController.animateTo(mMyLocationOverlay
            .getMyLocation());
      }
    });
  }

  /**
   * Fuegt den Overlay für die Anzeige des Geokontakts
   * zur Karte hinzu.
   */
  private void initGeokontaktOverlay() {
    final SharedPreferences prefs = PreferenceManager.
      getDefaultSharedPreferences(this);
    final String meinNick = 
      prefs.getString("nickname", "ich");
    mMapViewOverlay = new GeoKontaktOverlay(meinNick); 
    mMapView.getOverlays().add(mMapViewOverlay);
    mMapView.postInvalidate();
  }


  @Override
  protected void onStart() {
    super.onStart();
    
    ladeGeoKontakt();    
  }

  /**
   * Lädt den auf der Karte anzuzeigenden GeoKontakt.
   * Stellt sicher dass ein GeoKontakt zur Anzeige
   * in der Karte vorhanden.
   */
  private void ladeGeoKontakt() {
    final GeoKontaktSpeicher kontaktSpeicher = 
      new GeoKontaktSpeicher(this);   
    
    Log.d(TAG, "ladeGeoKontakt() aufgerufen...");
    if (mGeoKontaktId != 0) {
      mFreundKontakt =
        kontaktSpeicher.ladeGeoKontakt(mGeoKontaktId);    
      if (mFreundKontakt != null) {
        Log.d(TAG, "ladeGeoKontakt(): Kontakt geladen: " +
            mFreundKontakt.name);           
        Log.d(TAG, "ladeGeoKontakt(): Gps-Daten des " +
            "Kontakts: " +
            mFreundKontakt.letztePosition.toString());
      }  
    } else {
      //einfach den ersten (== neuesten) Kontakt holen
      Log.w(TAG, "ladeGeoKontakt(): Keine Kontakt-Id.");
      final Cursor c = kontaktSpeicher.
        ladeGeoKontaktListe(null);
      c.moveToFirst(); // FIXME: und wenn der false ist?
      mFreundKontakt = kontaktSpeicher.ladeGeoKontakt(
          c.getLong(c.getColumnIndex(GeoKontaktTbl.ID)));
    }
    kontaktSpeicher.schliessen();
  }
  
  /**
   * Ermittelt die eigene Mobilnummer aus den 
   * Systemeinstellungen.
   * 
   * @return Eigene Mobilnummer als String
   */
  private String ermittleEigeneMobilnummer() {
    final String eigeneMobilnummer = 
      TelefonnummernHelfer.ermittleEigeneMobilnummer(this);
    return TelefonnummernHelfer.bereinigeTelefonnummer(
        eigeneMobilnummer);
  }

  @Override
  protected void onResume() {
    Log.d(TAG, "onResume() aufgerufen...");
    // verbinde mit Service zum Uebertragen der Geoposition
    // nur dann, wenn die Position des Freundes nachverfolgt
    // werden soll:
    if (mPositionNachverfolgen) {
      final Intent netzwerkIntent = new Intent(this,
          NetzwerkServiceImpl.class);
      bindService(netzwerkIntent, 
          mNetzwerkServiceConnection, 
          Context.BIND_AUTO_CREATE);
    }  

    mMyLocationOverlay.enableMyLocation();
    mMyLocationOverlay.enableCompass();
    
    if (mSimulationsModus) {
      initalisiereEmulatorModus();  
    }  
     
    mMapView.invalidate();

    super.onResume();
  }

  /**
   * Falls die Karte im Simulationsmodus angezeigt wird,
   * erzeuge die eigene Position als Mock-Wert.
   */
  private void initalisiereEmulatorModus() {
    final double laengengrad = 
      mFreundKontakt.letztePosition.gpsData.mLaengengrad;
    final double breitengrad = 
      mFreundKontakt.letztePosition.gpsData.mBreitengrad;
    final double hoehe = 64.3;
    final long zeitstempel = System.currentTimeMillis();
    
    // Falls die Anwendung im Emulator läuft, soll die 
    // eigene Position simuliert werden, da der Emulator 
    // ohne Hilfe erst mal gar keine Position liefert:
    final String eigeneMobilnummer = 
      ermittleEigeneMobilnummer();
    // Emulator:
    if ("15555218135".equals(eigeneMobilnummer)) { 
      // setze die eigene Ortsposition für den Test:
      final LocationManager locationManager = 
        (LocationManager) getSystemService(
            Context.LOCATION_SERVICE);          
      final String mockProvider = 
        LocationManager.GPS_PROVIDER;
      
      if (locationManager.getProvider(mockProvider) != 
          null) {
        locationManager.addTestProvider(mockProvider, false,
            false, false, false, false, false, false, 0, 5);
        locationManager.setTestProviderEnabled(mockProvider,
            true);
        final GpsData eigenePosition = 
          new GpsData(laengengrad, breitengrad, hoehe, 
              zeitstempel);
        locationManager.setTestProviderLocation(
            mockProvider, eigenePosition.toLocation());
      } 
    }
  }
  
  @Override
  protected void onPause() {
    Log.d(TAG, "onPause() aufgerufen...");
    if (mPositionNachverfolgen) {
      mNetzwerkBinder.entferneAnzeigeCallbackHandler();
      unbindService(mNetzwerkServiceConnection);
    } 

    mMyLocationOverlay.disableMyLocation();
    mMyLocationOverlay.disableCompass();
    super.onPause();
  }

  @Override
  protected void onDestroy() {
    Log.d(TAG, "onDestroy() aufgerufen...");
    
    super.onDestroy();
  }

  /**
   * Zeichnet die Position des Geokontakts, eine 
   * Verbindungslinie zur eigenen Position  
   * und Beschriftungen.
   * 
   * @author mueller
   */
  public class GeoKontaktOverlay extends Overlay {
    
    /** Der Name zur Anzeige neben der eigenen Position. */
    private final String mMeinName;
  
    /** Punkt auf der Karte für den Bekannten. */
    private final Point mFreundPunkt = new Point();
    
    /** Punkt auf der Karte für die eigene Position. */
    private final Point mMeinPunkt = new Point();
    
    /** Zeichenbereich. */
    private final RectF mRect = new RectF();
  
    /** Zeichenbereich. */
    private final Paint mPaint = new Paint();
    
    /**
     * Konstruktor mit eigenem Namen.
     * @param meinNickname Eigener Name.
     */
    public GeoKontaktOverlay(final String meinNickname) {
      mMeinName = meinNickname;
    }
    
    @Override
    public void draw(Canvas canvas, MapView mapView,
        boolean shadow) {
      super.draw(canvas, mapView, shadow);
  
      final GeoPoint freundPosition = mFreundKontakt.
        letztePosition.gpsData.toGeoPoint();
  
      GeoPoint eigenePosition = mMyLocationOverlay.
        getMyLocation();
      
      if (eigenePosition == null) {
        eigenePosition = new GeoPoint((int) (51.3 * 1E6),
            (int) (7.2 * 1E6));
      }  
  
      // Wandele Geopunkt in Pixel um:
      // in Point werden die relativen Pixelkoordinaten
      // gesetzt:
      mapView.getProjection().toPixels(freundPosition,
          mFreundPunkt);
  
      mapView.getProjection().toPixels(eigenePosition,
          mMeinPunkt);
      
      float textWidth = mPaint.measureText(mMeinName);
      float textHeight = mPaint.getTextSize();
      mRect.set(mMeinPunkt.x + 13, 
          mMeinPunkt.y - textHeight,
          mMeinPunkt.x + 13 + 8 + textWidth, 
          mMeinPunkt.y + 4);
      mPaint.setARGB(128, 255, 255, 255);
      mPaint.setStyle(Style.FILL);
      canvas.drawRect(mRect, mPaint);
      
      mPaint.setARGB(255, 0, 0, 0);
      canvas.drawText(mMeinName, mMeinPunkt.x + 13 + 4, 
          mMeinPunkt.y, mPaint);
  
      // Verbindungslinie von eigener zur Freundposition 
      canvas.drawLine(mMeinPunkt.x, mMeinPunkt.y,
          mFreundPunkt.x, mFreundPunkt.y, mPaint);
  
      // Zeichenbereich definieren:
      mRect.set(mFreundPunkt.x - 5, mFreundPunkt.y + 5,
          mFreundPunkt.x + 5, mFreundPunkt.y - 5);
      
      // roter Punkt fuer eigenen Standort
      mPaint.setARGB(255, 200, 0, 30);
      mPaint.setStyle(Style.FILL);
      canvas.drawOval(mRect, mPaint);
  
      // schwarzen Kreis um den Punkt:
      mPaint.setARGB(255, 0, 0, 0);
      mPaint.setStyle(Style.STROKE);
      canvas.drawCircle(mFreundPunkt.x, mFreundPunkt.y, 5,
          mPaint);
  
      String freundName = mFreundKontakt.name;
      if (mFreundKontakt == null) {
        freundName = "Horst";
      } 
      textWidth = mPaint.measureText(freundName);
      textHeight = mPaint.getTextSize();
      mRect.set(mFreundPunkt.x + 8, 
          mFreundPunkt.y - textHeight,
          mFreundPunkt.x + 8 + 8 + textWidth, 
          mFreundPunkt.y + 4);
      mPaint.setARGB(128, 64, 64, 64);
      mPaint.setStyle(Style.FILL);
      canvas.drawRect(mRect, mPaint);
      mPaint.setARGB(255, 255, 255, 255);
      canvas.drawText(mFreundKontakt.name,
            mFreundPunkt.x + 8 + 4, mFreundPunkt.y, mPaint);
      
      // Log.d(TAG, "GeoKontaktOverlay->draw(): leave...");
    }
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    getMenuInflater().inflate(R.menu.karte_anzeigen, menu);

    return true;
  }

  @Override
  public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
      case R.id.opt_position_senden: 
        final Intent i = new Intent(this,
            PositionSenden.class);
        startActivity(i);
        return true;
      
      case R.id.opt_navigation_starten: 
        navigationStarten();
        return true;
  
      case R.id.opt_geokontakt_anrufen: 
        geoKontaktAnrufen();
        return true;
      
      case R.id.opt_hilfe: 
        final Intent j = new Intent(this, 
            HilfeAnzeigen.class);
        startActivity(j);
        return true;
  
      default: 
        Log.w(TAG, "unbekannte Option gewaehlt: " + item); 
        return super.onOptionsItemSelected(item);
    }
  }

  /**
   * Startet die Anzeige von Routeninformationen,
   * bzw. die Navigation über Google Navigation.
   */
  protected void navigationStarten()  {
    final String geoKontaktPosition = mFreundKontakt.
      letztePosition.gpsData.mBreitengrad + "," + 
      mFreundKontakt.letztePosition.gpsData.mLaengengrad;
    
    final String meinePosition = 
      mMyLocationOverlay.getMyLocation().
      getLatitudeE6() / 1E6 + "," + 
      mMyLocationOverlay.getMyLocation().getLongitudeE6() /
          1E6;
  
    Log.i(TAG, "Positionen: " + geoKontaktPosition + " " +
        meinePosition);
    
    // so kann Google Maps gestartet werden
    final Intent navigation = new Intent(Intent.ACTION_VIEW,
        Uri.parse("http://maps.google.com/maps?saddr=" +
            meinePosition + 
            "&daddr=" + 
            geoKontaktPosition));
    startActivity(navigation);
  
    // so kann Google Navigation gestartet werden
    // final Intent i = new Intent(Intent.ACTION_VIEW, 
    //    Uri.parse("google.navigation:q=" + 
    //        geoKontaktPosition));
    // startActivity(i);
  }
  
  /**
   * Startet einen Anruf des auf der Karte angezeigten 
   * GeoKontakts.
   */
  protected void geoKontaktAnrufen() {
    if (mFreundKontakt == null || 
        mFreundKontakt.mobilnummer == null ||
        mFreundKontakt.mobilnummer.length() == 0
        ) {
      Log.w(TAG, "Anruf nicht möglich, fehlende Daten");
      return;
    }
    final Intent intent = new Intent(Intent.ACTION_CALL, 
        Uri.parse("tel:" + mFreundKontakt.mobilnummer));
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    //intent.setClassName(mApp, 
    //PhoneApp.getCallScreenClassName());
    startActivity(intent);
  }
  
  @Override
  protected boolean isLocationDisplayed() {
    return super.isLocationDisplayed();
  }

  @Override
  protected boolean isRouteDisplayed() {
    return false;
  }

}
